#!/bin/bash

# 是否开启DEBUG模式
[[ -n "$DEBUG" ]] && set -x
# 是否指定JarFile和app_name
[[ -n "$JAR_FILE" ]] && jarfile="$JAR_FILE"
[[ -n "$APP_NAME" ]] && identity="$APP_NAME"

# 控制台输出颜色
echoRed() { echo -e $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo -e $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo -e $'\e[0;33m'"$1"$'\e[0m'; }
echoBlue() { echo -e $'\e[0;34m'"$1"$'\e[0m'; }

# 字符串转大写
transferCase() {
    echo "$1" | tr [a-z] [A-Z]
}

#################################################
## 目录结构为 working_dir:    #工作目录，即根目录
##             -bin/         #命令行目录COMMAND_DIR
##             -conf/        #配置文件目录
##             -lib/         #依赖jar包
##             -logs/        #日志目录
##             -jarfile.jar  #运行jar包
#################################################
# "$0"可能是个软连接
PRG="$0"
while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done

# 命令行目录
COMMAND_DIR="$(cd `dirname $PRG`; pwd)"
# 根目录
WORKING_DIR="$(dirname "$COMMAND_DIR")"
# jar文件的绝对路径，倒叙获取根目录下第一个jar包
[[ -z "$jarfile" ]] && JAR_FILE_PATH=`find "$WORKING_DIR" -maxdepth 1 -name '*.jar' | tail -1`
# 获取jar包的名字
JAR_FILE_NAME=$(basename "$JAR_FILE_PATH" '.jar')
[[ -n "$JAR_FILE_NAME" ]] || { echoRed "Jar not found, please check working home or [JAR_FILE] in config"; exit 1; }
# 获取config文件
CONFIG_FILE_NAME="$(basename "${JAR_FILE_NAME%.*}.conf")"
CONFIG_FILE=`find "$WORKING_DIR" -maxdepth 1 -name "$CONFIG_FILE_NAME" | tail -1`

[[ -n "${jarfile}" ]] || jarfile="$JAR_FILE_PATH"
[[ -n "${identity}" ]] || identity="$JAR_FILE_NAME"

#################################################
## 读取配置信息
##   grep -i(忽略大小写) -E "^${2}\s*="(正则表达式，匹配"参数(忽略空格)=") -m 1(第一行，即第一次出现);
##   2>/dev/null || echo "VAR=__UNDEFINED__" (错误信息输出为空，如果没有找到参数则输出"VAR=__UNDEFINED__");
##   head -n 1(第一行) cut -d '=' -f 2-(根据"="号截取后面的参数);
##   sed -e "s/^[[:space:]]*//;s/[[:space:]]*$//" (去除前后空格).
#################################################
readConfigFile(){
  (grep -i -E "^${2}\s*=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2- | sed -e "s/^[[:space:]]*//;s/[[:space:]]*$//";
}
#################################################
## 配置文件
##   使用示例 "$(config username root)",第一个参数username为配置文件中的参数，第二个参数root为默认值，可不写，即"$(config username)";
#################################################
config(){
  default="${2}"
  [[ -f "${CONFIG_FILE}" && -n "${1}" ]] || { [[ -n "${default}" ]] && echo -n $default; exit $?; }
  val="$(readConfigFile "${CONFIG_FILE}" "${1}")"
  if [[ $val = __UNDEFINED__ ]];
  then
    if [[ -n "${default}" ]];
	then
	  echo -n $default
    else
      echo -n ""
    fi
  else
    echo -n $val
  fi
}

# 配置文件参数
JAVA_HOME="$(config JAVA_HOME "$JAVA_HOME")"
JAVA_OPTS="$(config JAVA_OPTS "$JAVA_OPTS")"
RUN_USER="$(config RUN_USER "$RUN_USER")"
RUN_ARGS="$(config RUN_ARGS "$RUN_ARGS")"

PID_FOLDER="$(config PID_FOLDER)"
LOG_FOLDER="$(config LOG_FOLDER "$WORKING_DIR/logs")"
LOG_FILENAME="$(config LOG_FILENAME)"
STOP_WAIT_TIME="$(config STOP_WAIT_TIME)"
CLASSPATH_MODE="$(config CLASSPATH_MODE)"

[[ -z "$PID_FOLDER" ]] && PID_FOLDER="/var/run"
[[ -z "$LOG_FOLDER" ]] && LOG_FOLDER="/var/log"
! [[ "$PID_FOLDER" == /* ]] && PID_FOLDER="$(dirname "$identity")"/"$PID_FOLDER"
! [[ "$LOG_FOLDER" == /* ]] && LOG_FOLDER="$(dirname "$identity")"/"$LOG_FOLDER"
! [[ -x "$PID_FOLDER" ]] && PID_FOLDER="/tmp"
! [[ -x "$LOG_FOLDER" ]] && LOG_FOLDER="/tmp"

[[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="${identity}.log"
[[ -z "$STOP_WAIT_TIME" ]] && STOP_WAIT_TIME=60

# 确定脚本运行模式
action="$1"
shift 1

# 构建pid和log文件
PID_FOLDER="$PID_FOLDER/${identity}"
pid_file="$PID_FOLDER/${identity}.pid"
log_file="$LOG_FOLDER/$LOG_FILENAME"
TIME_FORMAT=$(date "+%Y-%m-%d")

# 确定运行用户
if [[ $(id -u) == "0" && -z "$RUN_USER" ]]; then
  run_user=$(ls -ld "$jarfile" | awk '{print $3}')
else
  run_user="$RUN_USER"
fi

# 查找并确定java
if [[ -n "$ " ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
  javaexe="$JAVA_HOME/bin/java"
elif type -p java > /dev/null 2>&1; then
  javaexe=$(type -p java)
elif [[ -x "/usr/bin/java" ]];  then
  javaexe="/usr/bin/java"
else
  echoRed "Unable to find Java"
  exit 1
fi

#拼凑完整的classpath参数
cp_mode="$(transferCase $CLASSPATH_MODE)"
APP_CLASSPATH="$WORKING_DIR/lib"
readClasspathDir(){
  for file in `ls $1`; do
    if [ -d "$1/$file" ]; then
      APP_CLASSPATH="${APP_CLASSPATH}":"${1}/${file}/*"
      readClasspathDir "$1/$file"
    fi
  done
}

[[ -n "$DEBUG" ]] && set +x
if [[ -d "$APP_CLASSPATH" && "$cp_mode" == "ONLY_JAR" ]]; then
  for i in "$WORKING_DIR"/lib/*.jar; do
    APP_CLASSPATH="$APP_CLASSPATH":"$i"
  done
else
  APP_CLASSPATH="${APP_CLASSPATH}/*"
  readClasspathDir "$APP_CLASSPATH"
fi
[[ -n "$DEBUG" ]] && set -x

# 运行参数
[[ -z "$JAVA_OPTS" ]] && JAVA_OPTS="-server -Xms768m -Xmx4096m -Xmn400m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC"
arguments=(-Dsun.misc.URLClassPath.disableJarChecking=true $JAVA_OPTS -classpath "$APP_CLASSPATH" -jar "$jarfile" $RUN_ARGS "$@")

##### 通用函数 #####
# 检查权限
checkPermissions() {
  touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; return 4; }
  touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; return 4; }
}
# 根据pid判断程序是否运行
isRunning() {
  [[ -n "$1" ]] || { exit 1; }
  ps -p "$1" &> /dev/null
}
# 等待
await_file() {
  end=$(date +%s)
  let "end+=10"
  while [[ ! -s "$1" ]]
  do
    now=$(date +%s)
    if [[ $now -ge $end ]]; then
      break
    fi
    sleep 1
  done
}

###################################################################

start() {
  if [[ -f "$pid_file" ]]; then
    pid=$(cat "$pid_file")
    isRunning "$pid" && { echoYellow "Already running [$pid]"; return 0; }
  fi
  do_start "$@"
}

do_start() {
  working_dir=$(dirname "$jarfile")
  pushd "$working_dir" > /dev/null
  mkdir -p "$PID_FOLDER" &> /dev/null
  if [[ -n "$run_user" ]]; then
    checkPermissions || return $?
    chown "$run_user" "$PID_FOLDER"
    chown "$run_user" "$pid_file"
    chown "$run_user" "$log_file"
    su -s /bin/sh -c "$javaexe $(printf "\"%s\" " "${arguments[@]}") >> \"$log_file\" 2>&1 & echo \$!" "$run_user" > "$pid_file"
    pid=$(cat "$pid_file")
  else
    checkPermissions || return $?
    "$javaexe" "${arguments[@]}" >> "$log_file" 2>&1 &
    pid=$!
    disown $pid
    echo "$pid" > "$pid_file"
  fi
  [[ -z $pid ]] && { echoRed "Failed to start"; rm -f "$pid_file"; return 1; }
  echoGreen "Started [$pid]"
}

stop() {
  working_dir=$(dirname "$jarfile")
  pushd "$working_dir" > /dev/null
  [[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
  pid=$(cat "$pid_file")
  isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
  do_stop "$pid" "$pid_file"
}

do_stop() {
  kill "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
  for i in $(seq 1 $STOP_WAIT_TIME); do
    isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
    [[ $i -eq STOP_WAIT_TIME/2 ]] && kill "$1" &> /dev/null
    sleep 1
  done
  echoRed "Unable to kill process $1";
  return 1;
}

force_stop() {
  [[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
  pid=$(cat "$pid_file")
  isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
  do_force_stop "$pid" "$pid_file"
}

do_force_stop() {
  kill -9 "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
  for i in $(seq 1 $STOP_WAIT_TIME); do
    isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
    [[ $i -eq STOP_WAIT_TIME/2 ]] && kill -9 "$1" &> /dev/null
    sleep 1
  done
  echoRed "Unable to kill process $1";
  return 1;
}

restart() {
  stop && start
}

force_reload() {
  working_dir=$(dirname "$jarfile")
  pushd "$working_dir" > /dev/null
  [[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; }
  pid=$(cat "$pid_file")
  rm -f "$pid_file"
  isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 7; }
  do_stop "$pid" "$pid_file"
  do_start
}

status() {
  working_dir=$(dirname "$jarfile")
  pushd "$working_dir" > /dev/null
  [[ -f "$pid_file" ]] || { echoRed "Not running"; return 3; }
  pid=$(cat "$pid_file")
  isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 1; }
  echoGreen "Running [$pid]"
  return 0
}

run() {
  pushd "$(dirname "$jarfile")" > /dev/null
  "$javaexe" "${arguments[@]}"
  result=$?
  popd > /dev/null
  return "$result"
}

info() {
  echoBlue "********System Info********"
  echo -e `head -n 1 /etc/issue`
  echo -e  `uname -a`
  echoBlue "*********Java Info*********"
  echo -e "JAVA_HOME=$javaexe"
  echo -e `$javaexe -version`
  echoBlue "*****Application Info******"
  echo -e "APP_HOME=$WORKING_DIR"
  echo -e "APP_NAME=$identity"
}

usage() {
  [[ -n "$action" ]] && echoRed "Unrecognized command line option: [$action] \n"
  echo -e "Usage: \n  $0 {start|stop|force-stop|restart|force-reload|status|run}"
  echo -e "\nCommand line options:"
  echo -e "  start            start java application in a separate window, and first verify that it has started"
  echo -e "  stop             stop java application, waiting up to 60 seconds for the process to end, and first verify that it has stopped"
  echo -e "  force-stop       stop java application, wait up to 60 seconds and then use kill -KILL if still running"
  echo -e "  restart          start java application, stop java application if still running"
  echo -e "  force-reload     restart java application if still running"
  echo -e "  status           view the running status for java application"
  echo -e "  run              start java application in the current window"

  echo -e "\nConfig file options:"
  echo -e "  JAVA_HOME        The location of the java executable is discovered by using the PATH by default.(default: JAVA_HOME in linux PATH)"
  echo -e "  JAVA_OPTS        Options that are passed to the JVM when it is launched.\n\t\t   (default: -server -Xms768m -Xmx4096m -Xmn400m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC)"
  echo -e "  RUN_USER         The name of linux user who runs the program.(default: <app_name>.jar file owner)"
  echo -e "  RUN_ARGS         The arguments to pass to the program."
  echo -e "  PID_FOLDER       The root name of the pid folder.(default: /var/run)"
  echo -e "  LOG_FOLDER       The name of the folder to put log files.(default: <app_home>/logs)"
  echo -e "  LOG_FILENAME     The name of the log file in the LOG_FOLDER.(default: <app_name>.log)"
  echo -e "  STOP_WAIT_TIME   The time in seconds to wait when stopping the application before forcing a shutdown.(default: 60s)"
}

case "$action" in
start)
  start "$@"; exit $?;;
stop)
  stop "$@"; exit $?;;
force-stop)
  force_stop "$@"; exit $?;;
restart)
  restart "$@"; exit $?;;
force-reload)
  force_reload "$@"; exit $?;;
status)
  status "$@"; exit $?;;
run)
  run "$@"; exit $?;;
info)
  info "$@"; exit $?;;
*)
  usage "$@"; exit 1;
esac

exit 0